{-# OPTIONS_GHC -Wall -Werror #-}
{-# LANGUAGE FlexibleContexts, TypeFamilies, 
  MultiParamTypeClasses, FunctionalDependencies #-}

module Projectable 
( ProjectX(..)
, ProjectY(..)
, ProjectZ(..)
, ProjectW(..)
, Project2(..)
, Project3(..)
, Project4(..)
, map2
, map3
, map4
, pzip2
, pzip3
, pzip4
, min2
, min3
, min4
, max2
, max3
, max4
) where

import Math

type Scalar2 = (Scalar,Scalar)
type Scalar3 = (Scalar,Scalar,Scalar)
type Scalar4 = (Scalar,Scalar,Scalar,Scalar)

class Project2 v where
  -- | Project out all coordinates
  project2 :: v -> Scalar2
  compose2 :: Scalar2 -> v

  {-# INLINE project2  #-}
  {-# INLINE compose2  #-}

class Project3 v where
  -- | Project out all coordinates
  project3 :: v -> Scalar3
  compose3 :: Scalar3 -> v

  {-# INLINE project3  #-}
  {-# INLINE compose3  #-}


class Project4 v where
  -- | Project out all coordinates
  project4 :: v -> Scalar4
  compose4 :: Scalar4 -> v

  {-# INLINE project4  #-}
  {-# INLINE compose4  #-}

class ProjectX v where
  -- | Project out the X coordinate of a vector
  getX :: v -> Scalar 

  -- | Transform the X coordinate of a vector
  mapX :: (Scalar->Scalar) -> v -> v

  -- | Replace the X coordinate of a vector
  withX :: Scalar -> v -> v
  withX = mapX . const

  {-# INLINE getX   #-}
  {-# INLINE mapX #-}
  {-# INLINE withX   #-}


class ProjectY v where
  -- | Project out the Y coordinate of a vector
  getY :: v -> Scalar

  -- | Transform the Y coordinate of a vector
  mapY :: (Scalar->Scalar) -> v -> v

  -- | Replace the Y coordinate of a vector
  withY :: Scalar -> v -> v
  withY = mapY . const

  {-# INLINE getY   #-}
  {-# INLINE mapY #-}
  {-# INLINE withY   #-}

class ProjectZ v where
  -- | Project out the Z coordinate of a vector
  getZ :: v -> Scalar 

  -- | Transform the Z coordinate of a vector
  mapZ :: (Scalar->Scalar) -> v -> v

  -- | Replace the Z coordinate of a vector
  withZ :: Scalar -> v -> v
  withZ = mapZ . const

  {-# INLINE getZ   #-}
  {-# INLINE mapZ #-}
  {-# INLINE withZ   #-}

class ProjectW v where
  -- | Project out the W coordinate of a vector
  getW :: v -> Scalar 

  -- | Transform the W coordinate of a vector
  mapW :: (Scalar->Scalar) -> v -> v

  -- | Replace the W coordinate of a vector
  withW :: Scalar -> v -> v
  withW = mapW . const

  {-# INLINE getW   #-}
  {-# INLINE mapW #-}
  {-# INLINE withW   #-}

map2 :: Project2 v => (Scalar2 -> Scalar2) -> v -> v
map2 f = compose2 . f . project2

map3 :: Project3 v => (Scalar3 -> Scalar3) -> v -> v
map3 f = compose3 . f . project3

map4 :: Project4 v => (Scalar4 -> Scalar4) -> v -> v
map4 f = compose4 . f . project4

{-# INLINE map2 #-}
{-# INLINE map3 #-}
{-# INLINE map4 #-}

pzip2 :: Project2 v => (Scalar->Scalar->Scalar) -> v -> v -> v 
pzip2 f a b = let (ax,ay) = project2 a
                  (bx,by) = project2 b
              in compose2 (f ax bx, f ay by)

pzip3 :: Project3 v => (Scalar->Scalar->Scalar) -> v -> v -> v 
pzip3 f a b = let (ax,ay,az) = project3 a
                  (bx,by,bz) = project3 b
              in compose3 (f ax bx, f ay by, f az bz)

pzip4 :: Project4 v => (Scalar->Scalar->Scalar) -> v -> v  -> v
pzip4 f a b = let (ax,ay,az,aw) = project4 a
                  (bx,by,bz,bw) = project4 b
              in compose4 (f ax bx, f ay by, f az bz, f aw bw)

{-# INLINE pzip2 #-}
{-# INLINE pzip3 #-}
{-# INLINE pzip4 #-}

min2 :: Project2 v => v -> v -> v 
min2 = pzip2 min

min3 :: Project3 v => v -> v -> v 
min3 = pzip3 min

min4 :: Project4 v => v -> v -> v 
min4 = pzip4 min

max2 :: Project2 v => v -> v -> v 
max2 = pzip2 max

max3 :: Project3 v => v -> v -> v 
max3 = pzip3 max

max4 :: Project4 v => v -> v -> v 
max4 = pzip4 max

